package org.infinispan.server.core.dataconversion;

import static java.nio.charset.StandardCharsets.US_ASCII;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.infinispan.commons.dataconversion.MediaType.APPLICATION_JSON;
import static org.infinispan.commons.dataconversion.MediaType.APPLICATION_OBJECT;
import static org.infinispan.commons.dataconversion.MediaType.APPLICATION_OCTET_STREAM;
import static org.infinispan.commons.dataconversion.MediaType.APPLICATION_UNKNOWN;
import static org.infinispan.commons.dataconversion.MediaType.APPLICATION_WWW_FORM_URLENCODED;
import static org.infinispan.commons.dataconversion.MediaType.TEXT_PLAIN;
import static org.infinispan.commons.dataconversion.StandardConversions.convertCharset;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;

import java.nio.charset.Charset;
import java.util.Collections;

import org.infinispan.commons.configuration.ClassAllowList;
import org.infinispan.commons.dataconversion.EncodingException;
import org.infinispan.commons.dataconversion.MediaType;
import org.infinispan.commons.util.Util;
import org.infinispan.test.data.Address;
import org.infinispan.test.data.Person;
import org.infinispan.test.dataconversion.AbstractTranscoderTest;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * @since 9.2
 */
@Test(groups = "functional", testName = "server.core.dataconversion.JsonTranscoderTest")
public class JsonTranscoderTest extends AbstractTranscoderTest {
   protected Person dataSrc;

   @BeforeClass(alwaysRun = true)
   public void setUp() {
      dataSrc = new Person("joe");
      Address address = new Address();
      address.setCity("London");
      dataSrc.setAddress(address);
      transcoder = new JsonTranscoder(new ClassAllowList(Collections.singletonList(".*")));
      supportedMediaTypes = transcoder.getSupportedMediaTypes();
   }

   @Test
   public void testEmptyContent() {
      byte[] empty = Util.EMPTY_BYTE_ARRAY;
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, APPLICATION_JSON, APPLICATION_JSON.withCharset(US_ASCII)));
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, APPLICATION_UNKNOWN, APPLICATION_JSON));
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, APPLICATION_OCTET_STREAM, APPLICATION_JSON));
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, APPLICATION_WWW_FORM_URLENCODED, APPLICATION_JSON));
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, TEXT_PLAIN, APPLICATION_JSON));
      assertArrayEquals(empty, (byte[]) transcoder.transcode(empty, APPLICATION_OBJECT, APPLICATION_JSON));
   }

   @Test
   public void testWWWFormUrlEncoded() {
      byte[] transcoded = (byte[]) transcoder.transcode("%7B%22k%22%3A%22v%22%7D", APPLICATION_WWW_FORM_URLENCODED, APPLICATION_JSON);
      assertEquals("{\"k\":\"v\"}", new String(transcoded));
   }

   @Test
   public void testCharset() {
      Charset korean = Charset.forName("EUC-KR");
      MediaType textPlainKorean = TEXT_PLAIN.withCharset(korean);
      MediaType jsonKorean = APPLICATION_JSON.withCharset(korean);
      MediaType textPlainAsString = TEXT_PLAIN.withClassType(String.class);
      MediaType jsonAsString = APPLICATION_JSON.withClassType(String.class);

      String content = "{\"city_kr\":\"서울\"}";
      byte[] contentUTF = content.getBytes(UTF_8);
      byte[] contentKorean = convertCharset(contentUTF, UTF_8, korean);

      byte[] result = (byte[]) transcoder.transcode(contentKorean, jsonKorean, TEXT_PLAIN);
      assertArrayEquals(result, contentUTF);

      String strResult = (String) transcoder.transcode(contentKorean, jsonKorean, textPlainAsString);
      assertEquals(strResult, content);

      result = (byte[]) transcoder.transcode(contentKorean, jsonKorean, textPlainKorean);
      assertArrayEquals(result, contentKorean);

      result = (byte[]) transcoder.transcode(contentKorean, textPlainKorean, APPLICATION_JSON);
      assertArrayEquals(result, contentUTF);

      strResult = (String) transcoder.transcode(contentKorean, jsonKorean, jsonAsString);
      assertEquals(strResult, content);

      result = (byte[]) transcoder.transcode(contentKorean, textPlainKorean, jsonKorean);
      assertArrayEquals(result, contentKorean);

      result = (byte[]) transcoder.transcode(contentUTF, TEXT_PLAIN, jsonKorean);
      assertArrayEquals(result, contentKorean);

      result = (byte[]) transcoder.transcode(content, textPlainAsString, jsonKorean);
      assertArrayEquals(result, contentKorean);

      result = (byte[]) transcoder.transcode(contentUTF, APPLICATION_JSON, TEXT_PLAIN);
      assertArrayEquals(result, contentUTF);

      result = (byte[]) transcoder.transcode(contentUTF, APPLICATION_JSON, textPlainKorean);
      assertArrayEquals(result, contentKorean);
   }

   private void assertTextToJsonConversion(String content) {
      final Object transcode = transcoder.transcode(content, TEXT_PLAIN, APPLICATION_JSON);
      assertArrayEquals((byte[]) transcode, content.getBytes(UTF_8));
   }

   @Test
   public void testTextToJson() {
      assertTextToJsonConversion("{\"a\":1}");
      assertTextToJsonConversion("1.5");
      assertTextToJsonConversion("\"test\"");
   }

   @Test(expectedExceptions = EncodingException.class)
   public void testPreventInvalidJson() {
      byte[] invalidContent = "\"field\" : value".getBytes(UTF_8);
      transcoder.transcode(invalidContent, TEXT_PLAIN, APPLICATION_JSON);
   }

   @Test(expectedExceptions = EncodingException.class)
   public void testPreventInvalidJson2() {
      byte[] invalidContent = "text".getBytes(UTF_8);
      transcoder.transcode(invalidContent, TEXT_PLAIN, APPLICATION_JSON);
   }

   @Test(expectedExceptions = EncodingException.class)
   public void testPreventInvalidJson3() {
      byte[] invalidContent = "{a:1}".getBytes(UTF_8);
      transcoder.transcode(invalidContent, TEXT_PLAIN, APPLICATION_JSON);
   }

}
